package com.nyist.aspect;

import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.nyist.CommonConstant;
import com.nyist.annotation.SysLog;
import com.nyist.utils.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 1.
 *
 * @ClassName LogAspect
 * @Description
 * @Author xiaoyang
 * @Date 2023/05/11/16:15
 */
@Slf4j
@Aspect
@Configuration
public class LogAspect {

    @Pointcut("@annotation(com.nyist.annotation.SysLog)")
    public void sysLogPointcut() {

    }

    @Around("sysLogPointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        // 执行方法
        Object result = point.proceed();
        // 执行时长(毫秒)
        long operationTime = System.currentTimeMillis() - beginTime;

        saveSysLog(point, operationTime, result);

        return result;
    }

    private void saveSysLog(ProceedingJoinPoint joinPoint, long time, Object obj) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        SysLog syslog = method.getAnnotation(SysLog.class);
        String methodDescription = syslog.value();
        int logType = syslog.logType();
        String logTypeName = getLogTypeName(logType);
        int operateType = syslog.operateType();
        String operateTypeName = getOperateTypeName(operateType);

        // 获取 包名.类名
        String className = joinPoint.getTarget().getClass().getName();
        // 请求的方法名
        String methodName = signature.getName();
        // 使用ctrl shift f 搜索 all 直达指定方法名称
        String methodLocation = className + "#" + methodName;

        // 获取request
        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
        String ip = ServletUtil.getClientIP(request);
        String requestUrl = request.getRequestURI().substring(request.getContextPath().length());
        String requestType = request.getMethod();
        String requestParam = getRequestParams(request, joinPoint);

        log.info("=========================== start >>>> {} <<<<< {}===========================", methodDescription, requestUrl);
        log.info("===>>>请求 日志类型 为: {} <<<===", logTypeName);
        log.info("===>>>请求 操作日志类型 为: {} <<<===", operateTypeName);
        log.info("===>>>请求 客户端IP 为: {} <<<===", ip);

        log.info("===>>>请求 方法位置 为: {} <<<===", methodLocation);
        log.info("===>>>请求 方法描述 为: {} <<<===", methodDescription);
        log.info("===>>>请求 地址 为: {} <<<===", requestUrl);
        log.info("===>>>请求 参数 为: {} <<<===", requestParam);
        log.info("===>>>请求 类型 为: {} <<<===", requestType);
        log.info("===>>>请求 耗时 为: {} ms<<<===", time);
        log.info("************************** end >>>> {} <<<<< **************************", methodDescription);
        log.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
        log.info(".");
        log.info(".");

    }

    private String getRequestParams(HttpServletRequest request, JoinPoint joinPoint) {
        String httpMethod = request.getMethod();
        String params = "";
        if (CommonConstant.HTTP_POST.equals(httpMethod)
                || CommonConstant.HTTP_PUT.equals(httpMethod)
                || CommonConstant.HTTP_PATCH.equals(httpMethod)) {
            Object[] paramsArray = joinPoint.getArgs();
            // java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
            //  https://my.oschina.net/mengzhang6/blog/2395893
            Object[] arguments = new Object[paramsArray.length];
            for (int i = 0; i < paramsArray.length; i++) {
                if (paramsArray[i] instanceof BindingResult || paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile) {
                    // ServletRequest不能序列化，从入参里排除，否则报异常：java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
                    // ServletResponse不能序列化 从入参里排除，否则报异常：java.lang.IllegalStateException: getOutputStream() has already been called for this response
                    continue;
                }
                arguments[i] = paramsArray[i];
            }
            // update-begin-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
            PropertyFilter profilter = (o, name, value) -> {
                int length = 500; // 指定保留的长度
                return value == null || value.toString().length() <= length;
            };
            // params = JSONObject.toJSONString(arguments, profilter);
            params = JSONObject.toJSONString(arguments, profilter);
            return params;
        } else {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            // 请求的方法参数值
            Object[] args = joinPoint.getArgs();
            // 请求的方法参数名称
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] paramNames = u.getParameterNames(method);
            if (args != null && paramNames != null) {
                for (int i = 0; i < args.length; i++) {
                    params += "  " + paramNames[i] + ": " + args[i];
                }
            }
            // params = Arrays.toString(joinPoint.getArgs());
            return JSONObject.toJSONString(params);
        }
    }


    private String getOperateTypeName(int operateType) {
        String targetName = "";
        if (operateType == 1) {
            targetName = "查询";
        } else if (operateType == 2) {
            targetName = "添加";
        } else if (operateType == 3) {
            targetName = "修改";
        } else if (operateType == 4) {
            targetName = "删除";
        } else if (operateType == 0) {
            targetName = "登录";
        } else if (operateType == -1) {
            targetName = "登出";
        } else {
            targetName = "其他";
        }
        return targetName;
    }

    private String getLogTypeName(int logType) {
        String targetName = "";
        if (logType == 0) {
            targetName = "操作日志";
        } else if (logType == 1) {
            targetName = "登录日志";
        } else if (logType == 2) {
            targetName = "定时任务";
        } else {
            targetName = "其他";
        }
        return targetName;
    }


}
